using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using System.Collections;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class LeftRecursionInformationSpecification {
        [Test]
        public void IsRecursiveInvocationShouldReturnTrueIffProductionOnStack () {
            var production = new Mock<IProduction> ().Object;

            var l = new LeftRecursionInformation ();

            Assert.IsFalse (l.CurrentlyParsing (production, 0));
            l.BeginParsing (production, 0);
            Assert.IsTrue (l.CurrentlyParsing (production, 0));
            l.EndParsing ();
            Assert.IsFalse (l.CurrentlyParsing (production, 0));
        }

        [Test]
        public void CurrentlyParsingShouldReturnFalseIfDifferentIndexGiven () {
            var production = new Mock<IProduction> ().Object;

            var l = new LeftRecursionInformation ();

            Assert.IsFalse (l.CurrentlyParsing (production, 0));
            l.BeginParsing (production, 0);
            Assert.IsFalse (l.CurrentlyParsing (production, 1));
            l.EndParsing ();
            Assert.IsFalse (l.CurrentlyParsing (production, 2));
        }

        [Test]
        public void DeclareRecursiveParseShouldMakeAllIntermediateProductionsInvolvedForThoseIndexesOnly () {
            var recursiveProduction = new Mock<IProduction> ().Object;
            var notInvolved1 = new Mock<IProduction> ().Object;
            var notInvolved2 = new Mock<IProduction> ().Object;
            var involved1 = new Mock<IProduction> ().Object;
            var involved2 = new Mock<IProduction> ().Object;

            var l = new LeftRecursionInformation ();
            
            l.BeginParsing (notInvolved1, 0);
            l.BeginParsing (recursiveProduction, 0);
            l.BeginParsing (involved1, 0);
            l.BeginParsing (notInvolved2, 1);
            l.EndParsing ();
            l.BeginParsing (involved2, 0);
            l.DeclareRecursiveParse (recursiveProduction, 0);

            Assert.IsTrue(l.IsInvolvedInRecursion (involved1, 0));
            Assert.IsTrue(l.IsInvolvedInRecursion (involved2, 0));

            Assert.IsFalse(l.IsInvolvedInRecursion (notInvolved1, 0));
            Assert.IsFalse(l.IsInvolvedInRecursion (notInvolved2, 1));
            Assert.IsFalse(l.IsInvolvedInRecursion (notInvolved2, 0));
            Assert.IsFalse(l.IsInvolvedInRecursion (recursiveProduction, 0));
            Assert.IsFalse(l.IsInvolvedInRecursion (new Mock<IProduction>().Object, 0));
            Assert.IsFalse(l.IsInvolvedInRecursion (involved1, 1));
            Assert.IsFalse(l.IsInvolvedInRecursion (involved2, 1));
        }

        [Test]
        public void DeclareRecursiveParseShouldMakeRecursiveProductionForThatIndexOnly () {
            var recursiveProduction = new Mock<IProduction> ().Object;
            var involved1 = new Mock<IProduction> ().Object;
            var involved2 = new Mock<IProduction> ().Object;

            var l = new LeftRecursionInformation ();
            
            l.BeginParsing (recursiveProduction, 0);
            l.BeginParsing (involved1, 0);
            l.BeginParsing (involved2, 0);
            l.DeclareRecursiveParse (recursiveProduction, 0);

            Assert.IsTrue (l.IsRecursiveProduction (recursiveProduction, 0));
            Assert.IsFalse (l.IsRecursiveProduction (recursiveProduction, 1));
        }

        [Test]
        public void CompletedRecursiveParseShouldClearAllRecursiveParseInformation () {
            var recursiveProduction = new Mock<IProduction> ().Object;
            var involved1 = new Mock<IProduction> ().Object;
            var involved2 = new Mock<IProduction> ().Object;

            var l = new LeftRecursionInformation ();
            
            // start parsing stuff
            l.BeginParsing (recursiveProduction, 0);
            l.BeginParsing (involved1, 0);
            l.BeginParsing (involved2, 0);

            // declare a recursive parse
            l.DeclareRecursiveParse (recursiveProduction, 0);

            // then, when the recursive parse is resolved, complete it.
            l.CompletedRecursiveParse (0);

            Assert.IsFalse (l.IsInvolvedInRecursion (involved1, 0));
            Assert.IsFalse (l.IsInvolvedInRecursion (involved2, 0));
            Assert.IsFalse (l.IsRecursiveProduction (recursiveProduction, 0));
        }

        [Test]
        public void MultipleRecursiveParsesShouldNotAffectEachOther () {
            var recursiveProduction = new Mock<IProduction> ().Object;
            var involved1 = new Mock<IProduction> ().Object;
            var involved2 = new Mock<IProduction> ().Object;

            var l = new LeftRecursionInformation ();
            
            // start parsing stuff
            l.BeginParsing (recursiveProduction, 0);
            l.BeginParsing (involved1, 0);
            l.BeginParsing (involved2, 0);

            // declare a recursive parse
            l.DeclareRecursiveParse (recursiveProduction, 0);

            l.BeginParsing (recursiveProduction, 3);
            l.BeginParsing (involved1, 3);
            l.BeginParsing (involved2, 3);
            // declare another recursive parse, further along
            l.DeclareRecursiveParse (recursiveProduction, 3);

            Assert.IsTrue (l.IsRecursiveProduction (recursiveProduction, 0));

            Assert.IsTrue (l.IsInvolvedInRecursion (involved1, 0));
            Assert.IsTrue (l.IsInvolvedInRecursion (involved2, 0));
            Assert.IsTrue (l.IsRecursiveProduction (recursiveProduction, 0));

            Assert.IsTrue (l.IsRecursiveProduction (recursiveProduction, 3));

            Assert.IsTrue (l.IsInvolvedInRecursion (involved1, 3));
            Assert.IsTrue (l.IsInvolvedInRecursion (involved2, 3));
            Assert.IsTrue (l.IsRecursiveProduction (recursiveProduction, 3));

            // then, when the recursive parse is resolved, complete it.
            l.CompletedRecursiveParse (0);

            Assert.IsFalse (l.IsInvolvedInRecursion (involved1, 0));
            Assert.IsFalse (l.IsInvolvedInRecursion (involved2, 0));
            Assert.IsFalse (l.IsRecursiveProduction (recursiveProduction, 0));

            Assert.IsTrue (l.IsInvolvedInRecursion (involved1, 3));
            Assert.IsTrue (l.IsInvolvedInRecursion (involved2, 3));
            Assert.IsTrue (l.IsRecursiveProduction (recursiveProduction, 3));
        }

        [Test]
        public void WhenTwoProductionsAreRecursiveTheirInvolvedProductionsAreAdded () {
            var l = new LeftRecursionInformation ();

            var recursive = new Mock<IProduction> ().Object;
            var involved1 = new Mock<IProduction> ().Object;
            var involved2 = new Mock<IProduction> ().Object;
            var involved3 = new Mock<IProduction> ().Object;

            l.BeginParsing (recursive, 0);

            l.BeginParsing (involved1, 0);
            l.DeclareRecursiveParse (recursive, 0);

            l.BeginParsing (involved2, 0);
            l.DeclareRecursiveParse (recursive, 0);

            Assert.IsTrue (l.IsRecursiveProduction (recursive, 0));
            Assert.IsFalse (l.IsInvolvedInRecursion (recursive, 0));
            Assert.IsTrue (l.IsInvolvedInRecursion (involved1, 0));
            Assert.IsTrue (l.IsInvolvedInRecursion (involved2, 0));
            Assert.IsFalse (l.IsInvolvedInRecursion (involved3, 0));
        }

        [Test]
        public void ShouldReturnProductionInvocationStack () {
            var l = new LeftRecursionInformation ();

            var production1 = new Mock<IProduction> ().Object;
            l.BeginParsing (production1, 1);

            var production2 = new Mock<IProduction> ().Object;
            l.BeginParsing (production2, 2);
            l.EndParsing ();

            var production3 = new Mock<IProduction> ().Object;
            l.BeginParsing (production3, 2);

            var invocations = l.ProductionInvocationStack;

            l.EndParsing ();
            l.EndParsing ();

            Assert.That (invocations.SequenceEqual (new[] {
                                                    new ProductionInvocation (production3, 2),
                                                    new ProductionInvocation (production1, 1),
                                                }));
        }

        [Test]
        public void ClearInvolvedProductionsShouldClearMemoTablesForGivenIndexForInvolvedProductions ()
        {
            var recursiveProduction = new Mock<IProduction>().Object;
            var involved1 = new Mock<IProduction>();
            var involved2 = new Mock<IProduction>();
            var source = new char[1];

            var memoTable1 = new Dictionary<int, ParseResult>();
            involved1.Setup(i => i.GetMemoTable(source)).Returns(memoTable1);
            var memoTable2 = new Dictionary<int, ParseResult>();
            involved2.Setup(i => i.GetMemoTable(source)).Returns(memoTable2);

            var l = new LeftRecursionInformation();

            l.BeginParsing(recursiveProduction, 0);
            l.BeginParsing(involved1.Object, 0);
            memoTable1[0] = new ParseResult(0, null, null);
            memoTable1[1] = new ParseResult(0, null, null);
            l.BeginParsing(involved2.Object, 0);
            memoTable2[0] = new ParseResult(0, null, null);
            memoTable2[1] = new ParseResult(0, null, null);

            l.DeclareRecursiveParse(recursiveProduction, 0);

            l.ClearInvolvedProductions(source, 0);

            Assert.That(memoTable1.ContainsKey(0), Is.False);
            Assert.That(memoTable1.ContainsKey(1));
            Assert.That(memoTable2.ContainsKey(0), Is.False);
            Assert.That(memoTable2.ContainsKey(1));
        }
    }
}